package layer.replication;

import layer.versioning.VersionedLimeTupleSpace;
import layer.versioning.VersionedTuple;
import lights.interfaces.ITuple;
import lime.AgentLocation;
import lime.ILimeAgent;
import lime.IllegalTupleSpaceNameException;

import lime.Location;
import lime.TupleSpaceEngineException;

public class ReplicableLimeTupleSpace {
	
	private static long n = 0;
	private VersionedLimeTupleSpace versionedLimeTupleSpace;
	private ILimeAgent agent;

	public ReplicableLimeTupleSpace(ILimeAgent agent)
		throws TupleSpaceEngineException, IllegalTupleSpaceNameException {
		this.agent = agent;
		versionedLimeTupleSpace = new VersionedLimeTupleSpace();
	}
	
	public ReplicableLimeTupleSpace(ILimeAgent agent, String name)
			throws TupleSpaceEngineException, IllegalTupleSpaceNameException {
		this.agent = agent;
		versionedLimeTupleSpace = new VersionedLimeTupleSpace(name);
	}
	
	public ReplicableTuple change(ReplicableTemplate template, ITuple t) throws TupleSpaceEngineException{
		ITuple tuple = this.resolveTemplate(template);
		ReplicableTuple replicableTuple = new ReplicableTuple(versionedLimeTupleSpace.change(tuple, t));
		this.setBack(tuple);
		return replicableTuple;
	}
	
	public String getName(){
		return versionedLimeTupleSpace.getName();
	}
	
	public ReplicableTuple in(ReplicableTemplate template) throws TupleSpaceEngineException{
		ITuple tuple = this.resolveTemplate(template);
		VersionedTuple versionedTuple = versionedLimeTupleSpace.in(tuple);
		this.setBack(tuple);
		return new ReplicableTuple(versionedTuple);
	}
	
	public ReplicableTuple in(Location current, AgentLocation destination, ReplicableTemplate template) throws TupleSpaceEngineException{
		ITuple tuple = this.resolveTemplate(template);
		VersionedTuple versionedTuple = versionedLimeTupleSpace.in(current, destination, tuple);
		this.setBack(tuple);
		return new ReplicableTuple(versionedTuple);
	}
	
	public ReplicableTuple[] ing(Location current, AgentLocation destination, ReplicableTemplate template) throws TupleSpaceEngineException{
		ITuple tuple = this.resolveTemplate(template);
		VersionedTuple versionedTuples[] = versionedLimeTupleSpace.ing(current, destination, tuple);
		this.setBack(tuple);
		ReplicableTuple[] ReplicableTuples = new ReplicableTuple[versionedTuples.length];
		for (int i=0;i<versionedTuples.length;i++)
			ReplicableTuples[i] = new ReplicableTuple(versionedTuples[i]);
		return ReplicableTuples;
	}
	
	public ReplicableTuple inp(Location current, AgentLocation destination, ReplicableTemplate template) throws TupleSpaceEngineException{
		ITuple tuple = this.resolveTemplate(template);
		VersionedTuple versionedTuple = versionedLimeTupleSpace.inp(current, destination, tuple);
		this.setBack(tuple);
		return new ReplicableTuple(versionedTuple);
	}
	
	public boolean isOwner(){
		return versionedLimeTupleSpace.isOwner();
	}
	
	public boolean isShared(){
		return versionedLimeTupleSpace.isShared();
	}
	
	public void out(AgentLocation destination, ITuple tuple)
	throws TupleSpaceEngineException {
		VersionedTuple versionedTuple = new VersionedTuple(n++);
		versionedTuple.setTuple(tuple);
		ReplicableTuple replicableTuple = new ReplicableTuple(agent, versionedTuple);
		// <VersionedTuple> -> <VersionedTuple, isReplica, origCur, origDest>
		tuple.add(replicableTuple.getIsReplica());
		tuple.addActual(replicableTuple.getOrigCur());
		tuple.addActual(replicableTuple.getOrigDest());
		versionedLimeTupleSpace.out(destination, tuple);
	}
	
	public void out(ITuple tuple) throws TupleSpaceEngineException {
		VersionedTuple versionedTuple = new VersionedTuple(n++);
		versionedTuple.setTuple(tuple);
		ReplicableTuple replicableTuple = new ReplicableTuple(agent, versionedTuple);
		// <VersionedTuple> -> <VersionedTuple, isReplica, origCur, origDest>
		tuple.add(replicableTuple.getIsReplica());
		tuple.addActual(replicableTuple.getOrigCur());
		tuple.addActual(replicableTuple.getOrigDest());
		versionedLimeTupleSpace.out(tuple);
		this.setBack(tuple);
	}
	
	public void outg(AgentLocation destination, ITuple[] tuples)
	throws TupleSpaceEngineException {
		for (ITuple tuple : tuples){
			VersionedTuple versionedTuple = new VersionedTuple(n++);
			versionedTuple.setTuple(tuple);
			ReplicableTuple replicableTuple = new ReplicableTuple(agent, versionedTuple);
			// <VersionedTuple> -> <VersionedTuple, isReplica, origCur, origDest>
			tuple.add(replicableTuple.getIsReplica());
			tuple.addActual(replicableTuple.getOrigCur());
			tuple.addActual(replicableTuple.getOrigDest());
		}
		versionedLimeTupleSpace.outg(destination, tuples);
		for (ITuple tuple : tuples)
			this.setBack(tuple);
	}
	
	public void print(){
		versionedLimeTupleSpace.print();
	}
	
	public ReplicableTuple rd(ReplicableTemplate template) throws TupleSpaceEngineException{
		ITuple tuple = this.resolveTemplate(template);
		VersionedTuple versionedTuple = versionedLimeTupleSpace.rd(tuple);
		this.setBack(tuple);
		return new ReplicableTuple(versionedTuple);
	}
	
	public ReplicableTuple rd(Location current, AgentLocation destination, ReplicableTemplate template) throws TupleSpaceEngineException{
		ITuple tuple = this.resolveTemplate(template);
		VersionedTuple versionedTuple = versionedLimeTupleSpace.rd(current, destination, tuple);
		this.setBack(tuple);
		return new ReplicableTuple(versionedTuple);
	}
	
	public ReplicableTuple[] rdg(Location current, AgentLocation destination, ReplicableTemplate template) throws TupleSpaceEngineException{
		ITuple tuple = this.resolveTemplate(template);
		VersionedTuple[] versionedTuples = versionedLimeTupleSpace.rdg(current, destination, tuple);
		this.setBack(tuple);
		ReplicableTuple[] replicableTuples = new ReplicableTuple[versionedTuples.length];
		for (int i=0;i<versionedTuples.length;i++)
			replicableTuples[i] = new ReplicableTuple(versionedTuples[i]);
		return replicableTuples;
	}
	
	public ReplicableTuple rdp(Location current, AgentLocation destination, ReplicableTemplate template) throws TupleSpaceEngineException{
		ITuple tuple = this.resolveTemplate(template);
		VersionedTuple versionedTuple = versionedLimeTupleSpace.rdp(current, destination, tuple);
		this.setBack(tuple);
		ReplicableTuple replicableTuple = new ReplicableTuple(versionedTuple);
		return replicableTuple;
	}
	
	private ITuple resolveTemplate(ReplicableTemplate template){
		ITuple tuple = template.getTuple();
		if (template.getMode().equals(ReplicableTemplate.TUPLEMODE.MASTER))
			tuple.addActual(0);
		else if (template.getMode().equals(ReplicableTemplate.TUPLEMODE.REPLICA))
			tuple.addActual(1); 
		else
			tuple.addFormal(Integer.class);
		tuple.addFormal(AgentLocation.class).addFormal(AgentLocation.class);
		return tuple;
	}
	
	private void setBack(ITuple tuple){
		tuple.removeAt(tuple.length()-1);
		tuple.removeAt(tuple.length()-1);
		tuple.removeAt(tuple.length()-1);
	}
	
	public boolean setShared(boolean isShared){
		return versionedLimeTupleSpace.setShared(isShared);
	}
}
